using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using UnityEngine;

namespace Script.Map
{
	public class MapGenerator : MonoBehaviour
	{
		private static class Dekoracja
		{
			public static int rozmiarBloku = 4;

			public static float procentPokryciaBloku = 0.07f;
		}

		private static class Roslina
		{
			public static int rozmiarBloku = 16;

			public static int ilosc = 60;
		}

		private static class PasmoGorskie
		{
			public static int maksymalnyRozmiar = 4;

			public static int minimalnyRozmiar = 1;

			public static float startowePrawdopodobienstwoLuki = 0.1f;

			public static float krokowePrawdopodobienstwoLuki = 0.2f;

			public static int rozmiarBloku = 16;

			public static int ilosc = 5;
		}

		private static class Jezioro
		{
			public static int minimalnyPoziomyRozmiar = 5;

			public static int maksymalnyPoziomyRozmiar = 15;

			public static int rozmiarBloku = 16;

			public static int ilosc = 5;
		}

		private static class Rzeka
		{
			public static int ilosc;

			public static int maksymalnaDlugoscOdcinka = 7;

			public static int minimalnaDlugoscOdcinka = 3;
		}

		private delegate int IsPlaceOccupied(int x, int y, int size = 0);

		public MapCreator mapCreator;

		[Header("Size")]
		public int height;

		public int width;

		private bool[,] grid;

		private int[,] groundTypes;

		private int[,] environmentTypes;

		private int amountOfEnvironmentObjects;

		private Land land;

		private StreamWriter stream;

		private int[,] array;

		private int playerPositionX;

		private int playerPositionY;

		private readonly int[][] combination = new int[4][]
		{
			new int[4] { 186, 184, 152, 507 },
			new int[4] { 186, 178, 176, 510 },
			new int[4] { 186, 58, 50, 447 },
			new int[4] { 186, 154, 26, 255 }
		};

		private readonly int[][] combinationW = new int[4][]
		{
			new int[4] { 186, 504, 216, 507 },
			new int[4] { 186, 438, 432, 510 },
			new int[4] { 186, 63, 54, 447 },
			new int[4] { 186, 219, 27, 255 }
		};

		private readonly int[] terrainAddition = new int[3] { 0, 1, 7 };

		private void Awake()
		{
			for (int i = 0; i < mapCreator.lands.Length; i++)
			{
				land = mapCreator.lands[i];
				SetVariables(land.Name);
				if (!Directory.Exists("Assets/Resources/Maps/" + land.Name))
				{
					Directory.CreateDirectory("Assets/Resources/Maps/" + land.Name);
				}
				for (int j = 0; j < 10; j++)
				{
					groundTypes = new int[width, height];
					CreateLakes();
					CreateRivers();
					grid = new bool[width, height];
					environmentTypes = new int[width, height];
					for (int k = 0; k < width; k++)
					{
						for (int l = 0; l < height; l++)
						{
							grid[k, l] = groundTypes[k, l] == 1;
						}
					}
					CreateMountainRanges();
					SetPlayerPosition();
					ClearPath();
					CreateFruitTrees();
					CreateDecoration();
					CreateDirt();
					stream = File.CreateText("Assets/Resources/Maps/" + land.Name + "/" + j + ".txt");
					WriteArgs(playerPositionX, playerPositionY);
					WriteArgs(width, height);
					SaveTerrain();
					SaveEnvironment();
					stream.Close();
				}
			}
		}

		private void SetVariables(string name)
		{
			if (name != null && name == "Desert")
			{
				Jezioro.ilosc = 4;
				Rzeka.ilosc = 1;
			}
			else
			{
				Jezioro.ilosc = 8;
				Rzeka.ilosc = 3;
			}
		}

		private void GetFreeSurface(int blockSize, out int x, out int y, IsPlaceOccupied operation, int size = 0)
		{
			int blockHeight;
			int blockWidth;
			GetBlocksSize(blockSize, out blockHeight, out blockWidth);
			int[,] occupationInfo = GetOccupationInfo(blockSize, operation, size);
			int[,] array = this.array;
			int num = int.MaxValue;
			int num2 = (int)((float)(blockSize * blockSize) * 0.8f);
			for (int i = 0; i < blockWidth; i++)
			{
				for (int j = 0; j < blockHeight; j++)
				{
					if (array == null || array[i, j] < num2)
					{
						num = Mathf.Min(num, occupationInfo[i, j]);
					}
				}
			}
			int num3 = 0;
			do
			{
				x = UnityEngine.Random.Range(0, blockWidth);
				y = UnityEngine.Random.Range(0, blockHeight);
			}
			while (occupationInfo[x, y] > num && num3++ < 10000);
			if (num3 >= 10000)
			{
				Debug.Log("!!!!!!!!!!!!!!!!!!\t\t" + num + "\t\t" + (array == null));
			}
		}

		private int[,] GetOccupationInfo(int blockSize, IsPlaceOccupied operation, int size = 0)
		{
			int blockHeight;
			int blockWidth;
			GetBlocksSize(blockSize, out blockHeight, out blockWidth);
			int[,] array = new int[blockWidth, blockHeight];
			for (int i = 0; i < width; i++)
			{
				for (int j = 0; j < height; j++)
				{
					array[i / blockSize, j / blockSize] += operation(i, j, size);
				}
			}
			return array;
		}

		private void GetBlocksSize(int blockSize, out int blockHeight, out int blockWidth)
		{
			blockHeight = height / blockSize;
			blockWidth = width / blockSize;
		}

		private int IsEnvironmentOccupied(int x, int y, int size = 0)
		{
			int num = size / 2;
			for (int i = -num; i <= num; i++)
			{
				for (int j = -num; j <= num; j++)
				{
					if (IsOutOfRange(x + i, y + j) || grid[x + i, y + j])
					{
						return (1 + size * 2) * (1 + size * 2);
					}
				}
			}
			return 0;
		}

		private int IsFoodOccupied(int x, int y, int size = 0)
		{
			return (environmentTypes[x, y] > 1).ToInt();
		}

		private int IsGroundOccupied(int x, int y, int size = 0)
		{
			return (groundTypes[x, y] == 1).ToInt();
		}

		private Dictionary<int, List<Vector2Int>> Convert(int[,] array)
		{
			Dictionary<int, List<Vector2Int>> dictionary = new Dictionary<int, List<Vector2Int>>();
			for (int i = 0; i < array.GetLength(0); i++)
			{
				for (int j = 0; j < array.GetLength(1); j++)
				{
					int key = array[i, j];
					if (key-- > 0)
					{
						if (!dictionary.ContainsKey(key))
						{
							dictionary.Add(key, new List<Vector2Int>());
						}
						dictionary[key].Add(new Vector2Int(i, j));
					}
				}
			}
			dictionary.OrderBy((KeyValuePair<int, List<Vector2Int>> o) => o.Key);
			return dictionary;
		}

		private void CreateDecoration()
		{
			int num = 2 + land.food.Length;
			int rozmiarBloku = Dekoracja.rozmiarBloku;
			int[,] occupationInfo = GetOccupationInfo(rozmiarBloku, IsEnvironmentOccupied);
			int num2 = (int)((float)(rozmiarBloku * rozmiarBloku) * Dekoracja.procentPokryciaBloku);
			for (int i = 0; i < occupationInfo.GetLength(0); i++)
			{
				for (int j = 0; j < occupationInfo.GetLength(1); j++)
				{
					while (occupationInfo[i, j]++ < num2)
					{
						int x;
						int y;
						do
						{
							x = UnityEngine.Random.Range(i * rozmiarBloku, (i + 1) * rozmiarBloku);
							y = UnityEngine.Random.Range(j * rozmiarBloku, (j + 1) * rozmiarBloku);
						}
						while (IsEnvironmentOccupied(x, y).ToBool());
						SetEnvironmentType(x, y, num + UnityEngine.Random.Range(0, land.environment.Length), 0);
					}
				}
			}
		}

		private void CreateFruitTrees()
		{
			int ilosc = Roslina.ilosc;
			int rozmiarBloku = Roslina.rozmiarBloku;
			array = GetOccupationInfo(rozmiarBloku, IsEnvironmentOccupied);
			while (ilosc-- > 0)
			{
				int x;
				int y;
				GetFreeSurface(rozmiarBloku, out x, out y, IsFoodOccupied);
				int x2;
				int y2;
				do
				{
					x2 = UnityEngine.Random.Range(x * rozmiarBloku, (x + 1) * rozmiarBloku);
					y2 = UnityEngine.Random.Range(y * rozmiarBloku, (y + 1) * rozmiarBloku);
				}
				while (IsEnvironmentOccupied(x2, y2).ToBool());
				SetEnvironmentType(x2, y2, 2 + UnityEngine.Random.Range(0, land.food.Length), 0);
			}
			array = null;
		}

		private void CreateMountainRange(int startX, int startY, int mountainSize)
		{
			float num = PasmoGorskie.startowePrawdopodobienstwoLuki;
			int num2 = 0;
			int num3 = UnityEngine.Random.Range(PasmoGorskie.minimalnyRozmiar, PasmoGorskie.maksymalnyRozmiar);
			for (int i = -num3; i <= num3; i++)
			{
				for (int j = -num2; j <= num2; j++)
				{
					int x = startX + i * 3;
					int y = startY + j * 3;
					if (UnityEngine.Random.value > num)
					{
						SetEnvironmentType(x, y, 1, mountainSize);
					}
				}
				num2 = ((i >= 0) ? (num2 - 1) : (num2 + 1));
				num += PasmoGorskie.krokowePrawdopodobienstwoLuki;
			}
		}

		private void CreateMountainRanges()
		{
			int num = 3;
			int rozmiarBloku = PasmoGorskie.rozmiarBloku;
			for (int i = 0; i < PasmoGorskie.ilosc; i++)
			{
				int x;
				int y;
				GetFreeSurface(rozmiarBloku, out x, out y, IsEnvironmentOccupied, num);
				int num2;
				int num3;
				do
				{
					num2 = UnityEngine.Random.Range(x * rozmiarBloku, (x + 1) * rozmiarBloku);
					num3 = UnityEngine.Random.Range(y * rozmiarBloku, (y + 1) * rozmiarBloku);
				}
				while (IsEnvironmentOccupied(num2, num3, num).ToBool());
				CreateMountainRange(num2, num3, num);
			}
		}

		private void SetEnvironmentType(int x, int y, int type, int size)
		{
			if (IsEnvironmentOccupied(x, y, size).ToBool())
			{
				return;
			}
			size /= 2;
			for (int i = -size; i <= size; i++)
			{
				for (int j = -size; j <= size; j++)
				{
					grid[x + i, y + j] = true;
				}
			}
			environmentTypes[x, y] = type;
			amountOfEnvironmentObjects++;
		}

		public void ClearPath()
		{
			int num = width * height / 1024;
			int num2 = (width - num * 2) / num;
			Vector2Int poczatek = new Vector2Int(playerPositionX, playerPositionY);
			Vector2Int[] array = new Vector2Int[4]
			{
				new Vector2Int(num, num),
				new Vector2Int(num, height - num),
				new Vector2Int(width - num, height - num),
				new Vector2Int(width - num, num)
			};
			for (int i = 0; i < 4; i++)
			{
				Vector2Int vector2Int = array[(i + 1) % 4];
				Vector2Int vector2Int2 = vector2Int - array[i];
				vector2Int2.Set(vector2Int2.x / num2, vector2Int2.y / num2);
				Vector2Int vector2Int3 = array[i];
				List<Vector2Int> list = new List<Vector2Int>();
				for (; vector2Int3 != vector2Int; vector2Int3 += vector2Int2)
				{
					if (!FindPath(poczatek, vector2Int3, true))
					{
						list.Add(vector2Int3);
					}
				}
				if (list.Count > 0)
				{
					FindPath(poczatek, list[UnityEngine.Random.Range(0, list.Count)], false);
				}
			}
		}

		public bool FindPath(Vector2Int poczatek, Vector2Int cel, bool test)
		{
			List<Vector2Int> list = new List<Vector2Int>();
			List<int> list2 = new List<int>();
			List<Vector2Int> list3 = new List<Vector2Int>();
			List<float> list4 = new List<float>();
			List<int> list5 = new List<int>();
			List<int> list6 = new List<int>();
			int num = 0;
			bool[,] array = new bool[width, height];
			Vector2Int[] array2 = new Vector2Int[4]
			{
				Vector2Int.up,
				Vector2Int.down,
				Vector2Int.right,
				Vector2Int.left
			};
			list3.Add(poczatek);
			list4.Add(0f);
			list5.Add(0);
			list6.Add(0);
			array[poczatek.x, poczatek.y] = true;
			int num2 = 0;
			while (true)
			{
				if (0 >= list3.Count)
				{
					return num2 < 15;
				}
				Vector2Int vector2Int = list3[0];
				list.Add(vector2Int);
				list2.Add(list5[0]);
				int num3 = list6[0];
				list3.RemoveAt(0);
				list4.RemoveAt(0);
				list5.RemoveAt(0);
				list6.RemoveAt(0);
				if (vector2Int == cel)
				{
					break;
				}
				for (int i = 0; i < 4; i++)
				{
					Vector2Int vector2Int2 = vector2Int + array2[i];
					if (!IsOutOfRange(vector2Int2.x, vector2Int2.y) && (!test || !grid[vector2Int2.x, vector2Int2.y]) && !array[vector2Int2.x, vector2Int2.y])
					{
						int num4 = num3 + grid[vector2Int2.x, vector2Int2.y].ToInt() * 100;
						float item = Vector2Int.Distance(vector2Int2, cel) + Vector2Int.Distance(vector2Int2, poczatek) + (float)num4;
						int num5 = list4.BinarySearch(item);
						if (num5 < 0)
						{
							num5 = ~num5;
						}
						list3.Insert(num5, vector2Int2);
						list4.Insert(num5, item);
						list5.Insert(num5, num);
						list6.Insert(num5, num4);
						array[vector2Int2.x, vector2Int2.y] = true;
					}
				}
				num++;
				num2++;
			}
			if (!test)
			{
				SciezkaWytyczenie(list, list2, num, poczatek, cel);
			}
			return true;
		}

		private void SciezkaWytyczenie(List<Vector2Int> lista, List<int> listaTworca, int id, Vector2Int start, Vector2Int meta)
		{
			Vector2Int vector2Int = meta;
			do
			{
				for (int i = -1; i < 2; i++)
				{
					for (int j = 1; j < 2; j++)
					{
						int num = vector2Int.x + i;
						int num2 = vector2Int.y + j;
						if (!IsOutOfRange(num, num2))
						{
							grid[num, num2] = false;
							environmentTypes[num, num2] = 0;
							if (groundTypes[num, num2] == 1)
							{
								groundTypes[num, num2] = 0;
							}
						}
					}
				}
				id = listaTworca[id];
				vector2Int = lista[id];
			}
			while (vector2Int != start);
		}

		private void Save()
		{
		}

		private void SaveEnvironment()
		{
			Dictionary<int, List<Vector2Int>> dictionary = Convert(environmentTypes);
			foreach (int key in dictionary.Keys)
			{
				WriteArgs(key, dictionary[key].Count);
				foreach (Vector2Int item in dictionary[key])
				{
					WriteArgs(item.x, item.y);
				}
			}
		}

		private void SaveTerrain()
		{
			int[] array = new int[width * height];
			while (true)
			{
				int num = 0;
				while (true)
				{
					if (num < width)
					{
						for (int i = 0; i < height; i++)
						{
							if (!SmoothTerrain(num, i, array))
							{
								goto end_IL_0048;
							}
						}
						num++;
						continue;
					}
					FixWater(array);
					int[] array2 = array;
					foreach (int id in array2)
					{
						Write(id);
					}
					return;
					continue;
					end_IL_0048:
					break;
				}
			}
		}

		private void CreateDirt()
		{
			int num = width * height / 24;
			for (int i = 0; i < num; i++)
			{
				int num2;
				int num3;
				do
				{
					num2 = UnityEngine.Random.Range(0, width);
					num3 = UnityEngine.Random.Range(0, height);
				}
				while (groundTypes[num2, num3] != 0);
				for (int j = -1; j < 2; j++)
				{
					for (int k = -1; k < 2; k++)
					{
						if (!IsOutOfRange(num2 + j, num3 + k) && groundTypes[num2 + j, num3 + k] == 0)
						{
							groundTypes[num2 + j, num3 + k] = 2;
						}
					}
				}
			}
		}

		private void CreateLake(int startX, int startY)
		{
			int num = 0;
			int num2 = UnityEngine.Random.Range(Jezioro.minimalnyPoziomyRozmiar, Jezioro.maksymalnyPoziomyRozmiar);
			int num3 = UnityEngine.Random.Range(1, 8 - num2 / 4);
			for (int i = 0; i < num2; i++)
			{
				for (int j = num; j < num3; j++)
				{
					int num4 = i + startX;
					int num5 = j + startY;
					if (IsOutOfRange(num4, num5))
					{
						continue;
					}
					for (int k = -1; k < 2; k++)
					{
						for (int l = -1; l < 2; l++)
						{
							if (!IsOutOfRange(num4 + k, num5 + l))
							{
								groundTypes[num4 + k, num5 + l] = 1;
							}
						}
					}
				}
				num += UnityEngine.Random.Range(-1, 2) * 2 + UnityEngine.Random.Range(-1, 2);
				num3 += UnityEngine.Random.Range(-1, 2) * 2 + UnityEngine.Random.Range(-1, 2);
			}
		}

		private void CreateLakes()
		{
			int rozmiarBloku = Jezioro.rozmiarBloku;
			for (int i = 0; i < Jezioro.ilosc; i++)
			{
				int x;
				int y;
				GetFreeSurface(rozmiarBloku, out x, out y, IsGroundOccupied);
				CreateLake(UnityEngine.Random.Range(x * rozmiarBloku, ++x * rozmiarBloku), UnityEngine.Random.Range(y * rozmiarBloku, ++y * rozmiarBloku));
			}
		}

		private void CreateRiver(int x, int y, int wayOut, bool yIsExit)
		{
			bool flag = Main.RandomBool();
			bool flag2 = false;
			int num = 0;
			while (true)
			{
				int num2 = UnityEngine.Random.Range(Rzeka.minimalnaDlugoscOdcinka, Rzeka.maksymalnaDlugoscOdcinka);
				if (flag)
				{
					int num3 = wayOut;
					if (flag2 && Main.RandomBool())
					{
						num2 = UnityEngine.Random.Range(0, num - 1);
						num3 *= -1;
					}
					if (yIsExit)
					{
						y += num3;
					}
					else
					{
						x += num3;
					}
					flag2 = !flag2;
					num = num2;
				}
				else
				{
					int num3 = Main.RandomSign();
					if (yIsExit)
					{
						x += num3;
					}
					else
					{
						y += num3;
					}
				}
				if (IsOutOfRange(x, y))
				{
					break;
				}
				for (int i = -1; i < 1; i++)
				{
					for (int j = -1; j < 1; j++)
					{
						if (!IsOutOfRange(x + i, y + j))
						{
							groundTypes[x + i, y + j] = 1;
						}
					}
				}
				flag = !flag;
			}
		}

		private void CreateRivers()
		{
			for (int i = 0; i < Rzeka.ilosc; i++)
			{
				int num;
				int num2;
				do
				{
					num = UnityEngine.Random.Range(0, width);
					num2 = UnityEngine.Random.Range(0, height);
				}
				while (groundTypes[num, num2] != 1);
				CreateRiver(num, num2, Main.RandomSign(), Main.RandomBool());
			}
		}

		private void Write(int id)
		{
			Write(id.ToString());
		}

		private void Write(string value)
		{
			stream.Write(value + "\n");
		}

		private void WriteArgs(params int[] args)
		{
			WriteArgs(Array.ConvertAll(args, (int o) => o.ToString()));
		}

		private void WriteArgs(params string[] args)
		{
			Write(string.Join("\t", args));
		}

		private void FixWater(int[] result)
		{
			int[,] array = new int[4, 2]
			{
				{ 5, 4 },
				{ 4, 5 },
				{ 4, 5 },
				{ 1, 4 }
			};
			for (int i = 1; i < width - 1; i++)
			{
				for (int j = 1; j < height - 1; j++)
				{
					int num = result[i * height + j] - 2;
					if (num < 0 || num > 3)
					{
						continue;
					}
					int num2 = num % 2;
					int num3 = -1;
					for (int k = -num2; k <= num2; k += 1 + num2)
					{
						for (int l = Mathf.Abs(k) - 1; l <= 1; l += 2)
						{
							int num4 = result[(i + k) * height + j + l] - 2;
							num3++;
							if (num4 >= 0 && num4 <= 3)
							{
								int value = num - num4;
								if (Mathf.Abs(value) == 2)
								{
									result[i * height + j] += array[num, num3];
									result[(i + k) * height + j + l] += array[num4, (num3 + 1) % 2];
									Debug.Log(i * 2 + "\t\t" + j * 2);
									break;
								}
							}
						}
					}
				}
			}
		}

		public bool IsOutOfRange(int x, int y)
		{
			if (x < 0 || x >= width || y < 0 || y >= height)
			{
				return true;
			}
			return false;
		}

		private bool SmoothTerrain(int x, int y, int[] array)
		{
			int num = groundTypes[x, y];
			int num2 = 0;
			if (num != 0)
			{
				int num3 = -1;
				int num4 = 0;
				for (int i = -1; i <= 1; i++)
				{
					for (int j = -1; j <= 1; j++)
					{
						num3++;
						if (IsOutOfRange(x + i, y + j) || num == groundTypes[x + i, y + j])
						{
							num2 |= 1 << num3;
							num4++;
						}
					}
				}
				bool flag = true;
				int num5;
				int num6 = (num5 = 0);
				int num7 = 33;
				if (num2 != 511)
				{
					int[][] array2 = ((num != 1) ? combination : combinationW);
					for (int k = 0; k < 4; k++)
					{
						for (int l = 0; l < array2[k].Length; l++)
						{
							if ((array2[k][l] & num2) == array2[k][l])
							{
								int num8 = NumberOfSetBits(array2[k][l] ^ num2);
								if (num8 < num7)
								{
									num7 = num8;
									num5 = k;
									num6 = l;
									flag = false;
								}
							}
						}
					}
					if (flag || (num6 == 0 && num == 1))
					{
						for (int m = -1; m <= 1; m++)
						{
							for (int n = -1; n <= 1; n++)
							{
								if (!IsOutOfRange(x + m, y + n))
								{
									if (num == 1)
									{
										grid[x + m, y + n] = false;
									}
									groundTypes[x + m, y + n] = 0;
								}
							}
						}
						return false;
					}
				}
				num2 = num * terrainAddition[num] + num5 + num6 * 4 - ((num6 != 0) ? 3 : 0);
				if (num2 >= 23)
				{
					num2 = num * terrainAddition[num];
				}
			}
			array[x * height + y] = num2;
			return true;
		}

		private int NumberOfSetBits(int i)
		{
			i -= (i >> 1) & 0x55555555;
			i = (i & 0x33333333) + ((i >> 2) & 0x33333333);
			return ((i + (i >> 4)) & 0xF0F0F0F) * 16843009 >> 24;
		}

		private void SetPlayerPosition()
		{
			int num = 8;
			int x;
			int y;
			GetFreeSurface(num, out x, out y, IsEnvironmentOccupied, 4);
			do
			{
				playerPositionX = UnityEngine.Random.Range(x * num, (x + 1) * num);
				playerPositionY = UnityEngine.Random.Range(y * num, (y + 1) * num);
			}
			while (IsEnvironmentOccupied(playerPositionX, playerPositionY, 4).ToBool());
		}
	}
}
